using System;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections;

namespace xListControls
{
	public class xListBox : ListBox, IPostBackDataHandler
	{
		#region Constant members
		private const string jsScript= @"<script type=""text/javascript"">
       //<![CDATA[
            // Add a new ListItem to the ListBosID_ADDED HiddenField
            function AddListItem(listName, text, value)
            { 
                var hiddenField = GetHiddenField(listName.id + '_ADDED');

                if (hiddenField != null)
                {
		            // Add a separator
		            var tmp = hiddenField.value;
		            if (tmp != '')
		               hiddenField.value += ',';

		            // Add the item to the hidden field
		            hiddenField.value += text + '|' + value; 
            		
		            // if present in the REMOVED hidden field, remove it
		            var removeHiddenField = GetHiddenField(listName.id + '_REMOVED');
		            if (removeHiddenField != null)
		            {
			            var removedItems = removeHiddenField.value.split(',');
			            removeHiddenField.value = '';
			            for (var i=0;i<removedItems.length;i++)
			            {
				            if (value != removedItems[i])
				            {
					            // Add a separator
					            var tmp1 = removeHiddenField.value;
					            if (tmp1 != '')
						            removeHiddenField.value += ',';

					            removeHiddenField.value += removedItems[i]; 
				            }
			            }			
		            }
	            }
            }

            // Removes an item from the ListBoxID_REMOVED HiddenField
            function RemoveListItem(listName, value)
            {   
                var hiddenField = GetHiddenField(listName.id + '_REMOVED');

                if (hiddenField != null)
                {
		            // Add a separator
		            var tmp = hiddenField.value;
		            if (tmp != '')
		               hiddenField.value += ',';

		            hiddenField.value += value; 
            		
		            // if present in the ADDED hidden field, remove it
		            var addHiddenField = GetHiddenField(listName.id + '_ADDED');
		            if (addHiddenField != null)
		            {
			            var addedItems = addHiddenField.value.split(',');
			            addHiddenField.value = '';
			            for (var i=0;i<addedItems.length;i++)
			            {
				            if (addedItems[i].match(value) == null)
				            {
					            // Add a separator
					            var tmp1 = addHiddenField.value;
					            if (tmp1 != '')
						            addHiddenField.value += ',';

					            addHiddenField.value += addedItems[i]; 
				            }
			            }			
		            }
                }
            }

            // Finds a hidden field on the page
            function GetHiddenField(fieldName)
            {
                var hiddenField;
                hiddenField = document.getElementById(fieldName);
                
                if (hiddenField != null)
                    return hiddenField;
                    
                return null;
            }          
        //]]>
      </script>";
		#endregion

		#region IPostBackDataHandler
		void IPostBackDataHandler.RaisePostDataChangedEvent()
		{
			OnSelectedIndexChanged(EventArgs.Empty);
		}

		/// <summary>
		/// Custom LoadPostData to:
		/// 1- Decide on the Selected item
		/// 2- Add any ListItems to the Items collection added on the client side
		/// 3- Remove any ListItems from the Items collection removed on the client side
		/// </summary>
		/// <param name="postDataKey"></param>
		/// <param name="postCollection"></param>
		/// <returns></returns>
		bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)
		{
			// Handle the SelectedIndex or Indicies
			string[] postedItems = postCollection.GetValues(postDataKey);
			bool returnValue = false;

			// If no selection is done on the client side
			if (postedItems == null)
			{
				if (this.SelectedIndex != -1)
				{	
					returnValue = true;
				}

                // No further processing for the Selected Index/Indicies
                goto HandleClientChanges;
			}

			// If selection is in single mode
			if (this.SelectionMode == ListSelectionMode.Single)
			{
				if (postedItems != null)
				{
					// Process first item, since we have a single selection
					// ListItem item = this.FindByValueInternal(postedItems[0]);
					int index = this.FindByValueInternal(postedItems[0]);//Items.IndexOf(item);
					if (this.SelectedIndex != index)
					{
						// Change occurred
						this.SelectedIndex = index;
						returnValue = true;
					}
				}
			}

			// Else, the selection mode is multiple
			int numberOfItemsSelected = postedItems.Length;

			// Old selected items
			ArrayList oldSelectedItems = this.SelectedIndicesInternal;

			// An empty arraylist of the currently selected items on the clisnde side
			ArrayList currentlySelectedItems = new ArrayList(numberOfItemsSelected);

			// Fill in all the indicies of the posted selected items
			for (int i=0; i<numberOfItemsSelected; i++)
			{
				currentlySelectedItems.Add(this.FindByValueInternal(postedItems[i]));
			}

			// Get the number of currently selected indicies
			int numberOfSelectedItems = 0;
			if (oldSelectedItems != null)
			{
				numberOfSelectedItems = oldSelectedItems.Count;
			}

			// Check if both numbers are equal
			if (numberOfItemsSelected == numberOfSelectedItems)
			{
				for (int j=0; j<numberOfItemsSelected; j++)
				{
					int oldSelect = Convert.ToInt32(currentlySelectedItems[j]);
					int currentSelect = Convert.ToInt32(oldSelectedItems[j]);

					if ( oldSelect != currentSelect)
					{
						// Explicitly mark the item selected
						this.Items[j].Selected = true;
						returnValue = true;
					}
				}
			}
			else
			{
				// The number of selected items has changed
				returnValue = true;
			}

			// Reset all selections to the newly selected items
			if (returnValue)
			{
				this.SelectInternal(currentlySelectedItems);
			}


// Section to handle ADDED and REMOVED Items.
HandleClientChanges:

			// Remove the items from the Items collection
			// that were marke for deletion on client side// Handle items added on Client Side
			string itemsRemoved = postCollection[this.HFItemsRemovedClientID];
			string[] itemsRemovedCol = itemsRemoved.Split(',');
			if (itemsRemovedCol != null)
			{
				if ((itemsRemovedCol.Length > 0) && (itemsRemovedCol[0] != ""))
				{
					for (int i = 0; i < itemsRemovedCol.Length; i++)
					{
						ListItem itemToRemove = this.Items.FindByValue(itemsRemovedCol[i]);

						// Remove from the Items Collection
						Items.Remove(itemToRemove);
					}
					returnValue = true;
				}
			}

			// Handle items added on Client Side
			string itemsAdded = postCollection[this.HFItemsAddedClientID];
			string[] itemsCol = itemsAdded.Split(',');
			if (itemsCol != null)
			{
				if ((itemsCol.Length > 0) && (itemsCol[0] != ""))
				{
					// counter to validate returnValue
					int counter = -1;
					for (int i = 0; i < itemsCol.Length; i++)
					{
						string buf = itemsCol[i];
						string[] itemsTokens = buf.Split('|');

						// Check if already added to the collection
						ListItem it = this.Items.FindByValue(itemsTokens[1]);
						if (it == null)
						{
							string text = itemsTokens[0];
							string id = itemsTokens[1];
							ListItem item = new ListItem(text, id);
							Items.Add(item);

							// Updata counter
							counter++;
						}
					}
					returnValue = counter > -1 ? true : false;
				}
			}

			return returnValue;
		}
		#endregion

		#region PreRender
		/// <summary>
		/// Add the HiddenFields required for this control to function properly:
		/// 1- _ADDED HiddenField to keep track of all the ListItems added on the client side
		/// 1- _REMOVED HiddenField to keep track of the values that have been removed on the client side
		/// Also, Force this control to fire LoadPostData upon PostBack.
		/// In addition, include a javascript file that includes utility methods to remove/add items
		/// from the ListBox.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);

			// Force the LoadPostData to fire always
			if (Page != null)
				Page.RegisterRequiresPostBack(this);

			// Register the hidden field to hold the added items
			Page.RegisterHiddenField(this.HFItemsAdded, "");

			// Register the hidden field to hold the indicies of removed items
			Page.RegisterHiddenField(this.HFItemsRemoved, "");

			// Register the JavaScript file that includes utility methods 
			if (!Page.IsClientScriptBlockRegistered("UtilMethods"))
				Page.RegisterClientScriptBlock("UtilMethods", jsScript);
		}
		#endregion

		#region Internal Properties
		/// <summary>
		/// Gets the name of the HiddenField that will store the added items
		/// </summary>
		protected string HFItemsAdded
		{
			get {
				string ieOnly = this.ClientID + "_ADDED";

				// Update - April 2nd 2007
				// if browser != IE, add the id proeprty
				// to the hidden field. This lacks the RegisterHiddenField method
				if (!HttpContext.Current.Request.Browser.ToString().Equals("IE"))
					return ieOnly + "\"" + " id=\"" + ieOnly;

				return ieOnly;
			}
		}

		protected string HFItemsAddedClientID
		{
			get
			{
				return this.ClientID + "_ADDED";
			}
		}

		/// <summary>
		/// Gets the name of the HiddenField that will store the indicies of remove items
		/// </summary>
		protected string HFItemsRemoved
		{
			get { 
				string ieOnly = this.ClientID + "_REMOVED";

				// Update - April 2nd 2007
				// if browser != IE, add the id proeprty
				// to the hidden field. This lacks the RegisterHiddenField method
				if (!HttpContext.Current.Request.Browser.ToString().Equals("IE"))
					return ieOnly + "\"" + " id=\"" + ieOnly;

				return ieOnly;
			}
		}

		protected string HFItemsRemovedClientID
		{
			get
			{
				return this.ClientID + "_REMOVED";
			}
		}
		#endregion

		#region Utility Properties
		/// <summary>
		/// Gets an ArrayList of all selected indicies in the ListBox
		/// </summary>
		internal virtual ArrayList SelectedIndicesInternal
		{
			get
			{
				ArrayList selectedItems = new ArrayList();
				// Loop through all items in the ListBox
				for (int i=0; i<this.Items.Count; i++)
				{
					if (this.Items[i].Selected)
					{
						// Add the index of the selected item
						selectedItems.Add(i);
					}
				}
				return selectedItems;
			}
		}

		#endregion

		#region Utility Methods
		internal int FindByValueInternal(string value)
		{
			int counter = 0;
			foreach (ListItem item1 in this.Items)
			{
				if (item1.Value.Equals(value))
				{
					return counter;
				}
				counter++;
			}
			return -1;
		}

		/// <summary>
		/// Selects required items
		/// </summary>
		/// <param name="selectedIndices"></param>
		internal void SelectInternal(ArrayList selectedIndices)
		{
			this.ClearSelection();
			for (int num1 = 0; num1 < selectedIndices.Count; num1++)
			{
				int num2 = (int) selectedIndices[num1];
				if ((num2 >= 0) && (num2 < this.Items.Count))
				{
					this.Items[num2].Selected = true;
				}
			}			
		}

		#endregion
	}
}